﻿// tga.cpp: 定义应用程序的入口点。
//

#include "tgaimage.h"
#include "logging.h"
#include "os_helper.h"

#include <fstream>
#include <iostream>

TGAImage::TGAImage(int w, int h, int bpp)
	: w(w), h(h), bpp(bpp), data(w * h * bpp, 0), intelbyteordering(os_helper::is_intel_byte_ordering())
{
}

TGAImage::TGAImage(int w, int h, int bpp, bool intelbyteordering)
	: w(w), h(h), bpp(bpp), data(w * h * bpp, 0), intelbyteordering(intelbyteordering)
{
}

bool TGAImage::read_from_tga_file(const std::string& filepath)
{
	std::ifstream in;
	in.open(filepath, std::ios::binary);
	if (!in.is_open()) {
		logging::error("can't open file {}", filepath);
		return false;
	}
	ensure_filebuffer_not_null();

	load_header(in);

	TGA_Header& header = filebuffer->header;
	if (header.idlength > 0) {
		if (!load_image_id(in)) {
			logging::error("an error occured while reading the image id");
			return false;
		}
	}
	w = header.imagewidth;
	h = header.imageheight;
	bpp = header.pixeldepth >> 3; // bits to bytes
	if (w <= 0 || h <= 0) {
		logging::error("bad width/height value");
		return false;
	}
	logging::info("w = {}, h = {}, bpp = {}", w, h, bpp);

	size_t nbytes = bpp * w * h;
	data = std::vector<uint8_t>(nbytes, 0);
	TgaImageType imagetype = static_cast<TgaImageType>(header.imagetype);
	if (ImageType_0 == imagetype) { // no image data
		logging::error("no image data");
		return true;
	}
	else if (ImageType_3 == imagetype || ImageType_2 == imagetype) { // not use RLE
		in.read(reinterpret_cast<char*>(data.data()), nbytes);
		if (!in.good()) {
			logging::error("an error occured while reading image data, not use RLE");
			return false;
		}
	}
	else if (ImageType_9 == imagetype || ImageType_10 == imagetype) { // use RLE
		if (!load_rle_data(in)) {
			logging::error("an error occured while reading image data, use RLE");
			return false;
		}
	}
	else if (ImageType_1 == imagetype) {

	}
	else {
		logging::error("unknown file format");
		return false;
	}

	if (!load_footer(in)) {
		logging::error("an error occured while reading footer");
		return false;
	}
	return true;
}

bool TGAImage::write_to_tga_file(const std::string& filepath, bool vflip, bool rle) const
{
	std::ofstream out;
	out.open(filepath, std::ios::binary);
	if (!out.is_open()) {
		logging::error("can't open file {}", filepath);
		return false;
	}

	if (!filebuffer) {
		return false;
	}

	// header
	if (!unload_header(out, vflip, rle)) {
		logging::error("can't unload header");
		return false;
	}

	// image id
	if (unload_image_id(out)) {
		logging::error("can't unload image id");
		return false;
	}

	// image/color map data
	if (!rle) { // not use RLE
		const void* pdata = data.data();
		out.write(static_cast<const char*>(pdata), w * h * bpp);
		if (!out.good()) {
			logging::error("can't unload raw data");
			return false;
		}
	}
	else if (!unload_rle_data(out)) { // use RLE
		logging::error("can't unload rle data");
		return false;
	}

	// develop area
	unload_develop_area(out);

	// extension area
	unload_extension_area(out);

	// footer
	unload_footer(out);

	return true;
}

void TGAImage::flip_horizontally()
{
}

void TGAImage::flip_vertically()
{
}

void TGAImage::set_image_id(const std::vector<uint8_t>& imageid)
{
	if (!filebuffer)
		filebuffer.reset(new TGAFileBuffer);

	std::copy(imageid.begin(), imageid.end(), std::back_inserter(filebuffer->data.imageid));
}

const std::vector<uint8_t>& TGAImage::image_id() const
{
	return filebuffer->data.imageid;
}

bool TGAImage::is_new_tga(const std::string& filepath)
{
	std::ifstream in;
	constexpr int signaturesize = sizeof(TGA_Footer::signature);
	char buffer[signaturesize];
	const std::string& signature = newtgasignature;

	in.open(filepath, std::ios::binary);
	if (!in.is_open()) {
		std::cerr << "can't open file " << filepath << LOG_LOCATION << std::endl;
		return false;
	}

	in.seekg(-signaturesize, std::ios::end);
	if (!in.good()) {
		std::cerr << "can't seek to the new tga signature position " << LOG_LOCATION << std::endl;
		return false;
	}

	in.read(buffer, sizeof(buffer));
	if (!in.good()) {
		std::cerr << "an error occured while reading signature " << LOG_LOCATION << std::endl;
		return false;
	}

	// compare signature
	if (signature.size() != strlen(buffer)) {
		return false;
	}
	for (int i = 0; i < signature.size(); i++) {
		if (signature[i] != buffer[i]) {
			return false;
		}
	}
	return true;
}

bool TGAImage::load_header(std::ifstream& in)
{
	TGA_Header& header = filebuffer->header;

	in.read(reinterpret_cast<char*>(&header), sizeof(header));
	if (!in.good()) {
		std::cerr << "an error occured while reading the header " << LOG_LOCATION << ":" << __LINE__ << std::endl;
		return false;
	}

	if (!intelbyteordering) {

	}
	return false;
}

bool TGAImage::load_image_id(std::ifstream& in)
{
	size_t len = filebuffer->header.idlength;
	in.read(reinterpret_cast<char*>(filebuffer->data.imageid.data()), len);
	if (!in.good()) {
		std::cerr << "an error occured while reading the image id " << LOG_LOCATION << std::endl;
		return false;
	}
	return false;
}

// Color map data
bool TGAImage::load_rle_data(std::ifstream& in)
{
	size_t pixelcount = w * h;
	size_t currentbyte = 0;
	TGAColor color;

	for (size_t currentpixel = 0; currentpixel < pixelcount; ) {
		uint8_t packetheader = in.get(); // read a packet header
		uint8_t repeatcount = (packetheader & 0x7F) + 0x1;

		if (packetheader & 0x80) { // RLE packet
			void* prgba = color.rgba;
			in.read(static_cast<char*>(prgba), bpp); // read a pixel
			if (!in.good()) {
				logging::error("an error occurred while reading the header");
				return false;
			}

			for (int i = 0; i < repeatcount; i++) {
				for (int t = 0; t < bpp; t++) {
					data[currentbyte++] = color.rgba[t];
				}
				currentpixel++;
				if (currentpixel > pixelcount) {
					logging::error("Too many pixels read");
					return false;
				}
			}
		}
		else { // Raw packet
			for (int i = 0; i < repeatcount; i++) {  // read repeatcount pixels
				void* prgba = color.rgba;
				in.read(static_cast<char*>(prgba), bpp);
				if (!in.good()) {
					logging::error("an error occurred while reading the header");
					return false;
				}

				for (int t = 0; t < bpp; t++) {
					data[currentbyte++] = color.rgba[t];
				}
				currentpixel++;
				if (currentpixel > pixelcount) {
					logging::error("Too many pixels read");
					return false;
				}
			}
		}
	}

	return true;
}

bool TGAImage::load_footer(std::ifstream& in)
{
	static const int footersize = sizeof(TGAFileBuffer::footer); // 26bytes
	char buffer[footersize] = {0};
	auto &footer = filebuffer->footer;
	static_assert(sizeof(buffer) == sizeof(TGA_Footer::signature) + 8, 
		"buffer size can't match footer signature size");

	auto markg = in.tellg();
	in.seekg(-footersize, std::ios::end);
	in.read(buffer, footersize);
	if (!in.good()) {
		std::cerr << "an error occured while reading footer " << LOG_LOCATION << std::endl;
		return false;
	}

	footer.extensionareaoffset = buffer[3] * 1000 + buffer[2] * 100 + buffer[1] * 10 + buffer[0];
	footer.developdirectoryoffset = buffer[7] * 1000 + buffer[6] * 100 + buffer[5] * 10 + buffer[4];
	std::copy(&buffer[8], &buffer[footersize], footer.signature);

	in.seekg(markg);
	return true;
}

bool TGAImage::unload_header(std::ofstream& out, bool vflip, bool rle) const
{
	TGA_Header header;
	header.idlength = filebuffer->data.imageid.size();
	header.pixeldepth = bpp << 3;
	header.imagewidth = w;
	header.imageheight = h;
	header.imagetype = (bpp == GRASCALE ? (rle ? 11 : 3) : (rle ? 10 : 2));
	header.imagedescriptor = vflip ? 0x00 : 0x20; // top-left or bottom-left origin
	const void* pheader = &header;
	out.write(static_cast<const char*>(pheader), sizeof(header));
	if (!out.good()) {
		logging::error("can't dump the tga file");
		return false;
	}

	return true;
}

bool TGAImage::unload_image_id(std::ofstream& out) const
{
	int nbytes = filebuffer->header.idlength;
	const void* pimageid = filebuffer->data.imageid.data();
	out.write(static_cast<const char*>(pimageid), nbytes);
	if (!out.good()) {
		logging::error("can't unload image id");
		return false;
	}
	return true;
}

bool TGAImage::unload_rle_data(std::ofstream& out) const
{
	return false;
}

bool TGAImage::unload_develop_area(std::ofstream& out) const
{
	if (!filebuffer) return false;

	const void* pfields = filebuffer->developarea.developfields.data();
	size_t fieldsize = filebuffer->developarea.developfields.size();
	out.write(static_cast<const char*>(pfields), fieldsize);
	if (!out.good()) {
		logging::error("can't upload develop fields");
		return false;
	}

	const void* pdirectory = filebuffer->developarea.developdirectory.data();
	size_t directorysize = filebuffer->developarea.developdirectory.size();
	out.write(static_cast<const char*>(pdirectory), directorysize);
	if (!out.good()) {
		logging::error("can't upload develop directory");
		return false;
	}
	return true;
}

bool TGAImage::unload_extension_area(std::ofstream& out) const
{
	if (!filebuffer) return false;

	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.extensionsize), sizeof(filebuffer->extensionarea.extensionsize));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.authorname), sizeof(filebuffer->extensionarea.authorname));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.authorcomment), sizeof(filebuffer->extensionarea.authorcomment));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.datetimestamp), sizeof(filebuffer->extensionarea.datetimestamp));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.jobtime), sizeof(filebuffer->extensionarea.jobtime));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.softwareid), sizeof(filebuffer->extensionarea.softwareid));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.softwareversion), sizeof(filebuffer->extensionarea.softwareversion));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.keycolor), sizeof(filebuffer->extensionarea.keycolor));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.pixelaspectratio), sizeof(filebuffer->extensionarea.pixelaspectratio));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.gammavalue), sizeof(filebuffer->extensionarea.gammavalue));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.colorcorrectionoffset), sizeof(filebuffer->extensionarea.colorcorrectionoffset));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.pastagestampoffset), sizeof(filebuffer->extensionarea.pastagestampoffset));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.scanlineoffset), sizeof(filebuffer->extensionarea.scanlineoffset));
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.attributetype), sizeof(filebuffer->extensionarea.attributetype));
	// varaible length
	out.write(reinterpret_cast<char*>(filebuffer->extensionarea.scanlinetable.data()), filebuffer->extensionarea.scanlinetable.size());
	out.write(reinterpret_cast<char*>(filebuffer->extensionarea.patagestampimage.data()), filebuffer->extensionarea.patagestampimage.size());
	// array with fixed length
	out.write(reinterpret_cast<char*>(&filebuffer->extensionarea.colorcorrectiontable), sizeof(filebuffer->extensionarea.colorcorrectiontable));

	if (!out.good()) {
		logging::error("can't unload extension area");
		return false;
	}
	return true;
}

bool TGAImage::unload_footer(std::ofstream& out) const
{
	if (!filebuffer) return false;
	uint32_t developareaoffset = sizeof(TGA_Header) + filebuffer->data.imageid.size() + filebuffer->data.colormapdata.size() + filebuffer->data.imagedata.size();
	uint32_t extensionareaoffset = developareaoffset + filebuffer->developarea.developfields.size() + filebuffer->developarea.developdirectory.size();
	filebuffer->footer.developdirectoryoffset = developareaoffset;
	filebuffer->footer.extensionareaoffset = extensionareaoffset;
	std::copy(newtgasignature.begin(), newtgasignature.end(), std::begin(filebuffer->footer.signature));
	filebuffer->footer.signature[sizeof(filebuffer->footer.signature) - 1] = '\0';

	out.write(reinterpret_cast<char*>(&filebuffer->footer.extensionareaoffset), sizeof(filebuffer->footer.extensionareaoffset));
	out.write(reinterpret_cast<char*>(&filebuffer->footer.developdirectoryoffset), sizeof(filebuffer->footer.developdirectoryoffset));
	out.write(reinterpret_cast<char*>(filebuffer->footer.signature), sizeof(filebuffer->footer.signature));

	if (!out.good()) {
		logging::error("can't unload footer area");
		return false;
	}
	return true;
}

void TGAImage::ensure_filebuffer_not_null()
{
	if (!filebuffer) {
		filebuffer.reset(new TGAFileBuffer);
	}
}
